package org.aceor.mddal.gateway.router;

import com.google.common.base.Strings;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import org.aceor.mddal.common.sqlparser.result.SQLParsedResult;
import org.aceor.mddal.common.sqlparser.result.context.ShardingContext;
import org.aceor.mddal.common.sqlparser.result.object.Column;
import org.aceor.mddal.config.sharding.LogicTable;
import org.aceor.mddal.config.sharding.TableRule;
import org.aceor.mddal.gateway.executor.ExecuteContext;
import org.aceor.mddal.gateway.router.handler.*;
import org.apache.commons.collections.CollectionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;

/**
 * Created by lxue on 16/4/11.
 */
public class Router {

    private static final Logger log = LoggerFactory.getLogger(Router.class);

    private final LogicTable logicTable;

    private volatile static Router router;

    //路由策略映射
    private static Map<Class<? extends RouteHandler>, RouteHandler> routeHandlerMap = Maps.newHashMap();

    static {
        //初始化路由策略映射
        routeHandlerMap.put(JustDBShardingRouteHandler.class, new JustDBShardingRouteHandler());
        routeHandlerMap.put(JustTBShardingRouteHandler.class, new JustTBShardingRouteHandler());
        routeHandlerMap.put(NoShardingRouteHandler.class, new NoShardingRouteHandler());
        routeHandlerMap.put(TBDBShardingRouteHandler.class, new TBDBShardingRouteHandler());
    }

    //单例需要,私有化构造器
    private Router(LogicTable logicTable) {
        this.logicTable = logicTable;
    }

    public static Router getInstance(LogicTable logicTable) {
        //单例
        if (null == router) {
            synchronized (Router.class) {
                if (null == router) {
                    if (null == logicTable) {
                        throw new NullPointerException("第一次创建Router对象时logicTable不能为空!");
                    }
                    //设置逻辑表对象
                    router = new Router(logicTable);
                }
            }
        }
        return router;
    }


    public ExecuteContext route(SQLParsedResult sqlParsedResult, String originalSql, List<Object> args) {
        if (null == sqlParsedResult) {
            throw new RuntimeException("sql :" + originalSql + "解析出错");
        }
        ExecuteContext executeContext;

        /**
         * 1. 设置sqlmap
         */
        RouteHandlerParam param = new RouteHandlerParam(sqlParsedResult, originalSql, args, logicTable.getDefaultDbIndex());
        switch (sqlParsedResult.shardingContext.getShardingType()) {
            /**
             * 1. 没有使用分库分表
             */
            case NO_SHARDING: {
                executeContext = routeHandlerMap.get(NoShardingRouteHandler.class).route(param);
                break;
            }

            /**
             * 2. 只分库不分表
             */
            case JUST_DB_SHARDING: {
                executeContext = routeHandlerMap.get(JustDBShardingRouteHandler.class).route(param);
                break;
            }

            /**
             * 3. 只分表
             */
            case JUST_TB_SHARDING: {
                executeContext = routeHandlerMap.get(JustTBShardingRouteHandler.class).route(param);
                break;
            }

            /**
             * 4.又分库又分表
             */
            case TB_DB_BOTH_SHARDING: {
                executeContext = routeHandlerMap.get(TBDBShardingRouteHandler.class).route(param);
                break;
            }

            default:
                throw new IllegalArgumentException("不支持的ShardingType :" + sqlParsedResult.shardingContext.getShardingType());
        }

        /**
         * 设置路由上下文
         */
        executeContext.mergeContext = sqlParsedResult.mergeContext;
        return executeContext;
    }

    /**
     * sql解析时会使用到
     *
     * @param name
     * @param isTb 是否指分表字段.false表示是分库字段
     * @return
     */
    public Collection<Column> getShardingColumns(String name, boolean isTb) {
        TableRule tableRule = logicTable.getTableRules().get(name);
        if (null == tableRule) {
            log.info("表 :{} 未做分库分库 ==> 没有找到分库分表字段", name);
            return Collections.emptyList();
        } else {
            Collection<Column> columns = Sets.newHashSet();
            if (isTb) {
                if (null != tableRule.getTbRule()
                        && !Strings.isNullOrEmpty(tableRule.getTbRule().getShardingColumn())) {
                    String tbShardingColumn = tableRule.getTbRule().getShardingColumn();
                    log.info("表 :{} 使用了分表规则,分表字段 :{}", name, tbShardingColumn);
                    columns.add(new Column(tbShardingColumn));
                }
            } else {
                if (null != tableRule.getDbRule()
                        && !Strings.isNullOrEmpty(tableRule.getDbRule().getShardingColumn())) {
                    String dbShardingColumn = tableRule.getDbRule().getShardingColumn();
                    log.info("表 :{} 使用了分库规则,分库字段 :{}", name, dbShardingColumn);
                    columns.add(new Column(dbShardingColumn));
                }
            }
            return columns;
        }
    }

    /**
     * 暂时支持分库分表字段单个
     *
     * @return map key: table名, value: 分库分表上下文
     */
    public Map<String, ShardingContext> getAllTableShardingColumnsMapping() {
        if (logicTable.getTableRules().isEmpty()) {
            return Collections.emptyMap();
        }
        Map<String, ShardingContext> mapping = Maps.newHashMap();
        for (Map.Entry<String, TableRule> entry : logicTable.getTableRules().entrySet()) {
            String tableName = entry.getKey();
            ShardingContext shardingContext = new ShardingContext();
            shardingContext.logicTableName = tableName;
            Collection<Column> shardingColumns = getShardingColumns(tableName, true);
            if (CollectionUtils.isNotEmpty(shardingColumns)) {
                shardingContext.tbShardingColumn = shardingColumns.iterator().next().getName();
                shardingContext.tbShardingFunction = entry.getValue().getTbRule();
                shardingContext.tbNameMessageFormat = entry.getValue().getTbNameMessageFormat();
            }
            shardingColumns = getShardingColumns(tableName, false);
            if (CollectionUtils.isNotEmpty(shardingColumns)) {
                shardingContext.dbShardingColumn = shardingColumns.iterator().next().getName();
                shardingContext.dbShardingFunction = entry.getValue().getDbRule();
                shardingContext.dbNameMessageFormat = entry.getValue().getDbNameMessageFormat();
            }
            mapping.put(tableName, shardingContext);
        }
        return mapping;
    }
}
